package com.leyou.search.service;

import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.vo.PageResult;
import com.leyou.item.clients.ItemClient;
import com.leyou.item.dto.BrandDTO;
import com.leyou.item.dto.CategoryDTO;
import com.leyou.item.dto.SpecParamDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private ElasticsearchTemplate esTemplate;


    public PageResult<Goods> loadData(SearchRequest searchRequest) {
        /**
         * 数据从哪里来？？？，elasticsearch
         */

        QueryBuilder query = buildBasicQuery(searchRequest);

        //查询拼接工具
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //添加查询条件
        queryBuilder.withQuery(query);

        //添加分页条件,springData中所有的分页计算从0开始计算，但是常规使用从1开始，所以所有的springData的业务分页，都要给前端传入的当前页-1
        queryBuilder.withPageable(PageRequest.of(searchRequest.getPage() - 1, searchRequest.getSize()));


        //执行查询，获取到"聚合分页"等查询结果,面向父类编程,返回结果可以囊括所有的工况场景
        AggregatedPage<Goods> goodsAggregatedPage = this.esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //封装分页查询的返回结果

        return new PageResult<>(goodsAggregatedPage.getTotalElements(), goodsAggregatedPage.getTotalPages(), goodsAggregatedPage.getContent());
    }

    /**
     * 可搜索规格参数的查询，聚合
     *
     * @param searchRequest
     * @return
     */
    public Map<String, List<?>> loadFilter(SearchRequest searchRequest) {

        //有次序的map
        Map<String, List<?>> resultMap = new LinkedHashMap<>();

        QueryBuilder query = buildBasicQuery(searchRequest);

        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        queryBuilder.withQuery(query);

        //springData不允许出现分页查询，不展示内容
        queryBuilder.withPageable(PageRequest.of(0, 1));

        String brandAggName = "brands";
        String categoryAggName = "categories";

        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("categoryId"));

        //获取聚合以及分页的查询结果
        AggregatedPage<Goods> goodsAggregatedPage = this.esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //获取到所有的聚合
        Aggregations aggregations = goodsAggregatedPage.getAggregations();

        //根据聚合名称获取聚合结果,xxxTerms返回结果取决于，参与聚合的字段，如果字段的是string则结果就是StringTerms
        LongTerms brandTerms = aggregations.get(brandAggName);

        //获取聚合分桶,先获取到聚合分桶，然后，循环取值，key的类型原形是object，get取值，变成了number，变成long
        List<Long> brandIds = brandTerms.getBuckets()
                .stream()
                .map(bucket -> bucket.getKeyAsNumber())
                .map(Number::longValue)
                .collect(Collectors.toList());

        //TODO 健壮性判断，发起请求时，参数不能为空

        //根据品牌id集合查询对应的品牌集合
        List<BrandDTO> brandDTOS = this.itemClient.queryBrandByIds(brandIds);

        LongTerms categoryTerms = aggregations.get(categoryAggName);

        List<Long> categoryIds = categoryTerms.getBuckets()
                .stream()
                .map(LongTerms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());

        List<CategoryDTO> categoryDTOS = this.itemClient.queryCategoryByIds(categoryIds);

        resultMap.put("分类", categoryDTOS);
        resultMap.put("品牌", brandDTOS);

        //要展示其他可搜索规格参数，需要分类唯一，确定
        if (null != categoryIds && 1 == categoryIds.size()) {

            //三个参数，分类id，查询条件，查询结果的封装
            getSpecs(categoryIds.get(0), query, resultMap);
        }


        return resultMap;
    }

    private void getSpecs(Long cid, QueryBuilder query, Map<String, List<?>> resultMap) {

        //根据分类查询其对应的可搜索规格参数
        List<SpecParamDTO> specParamDTOS = this.itemClient.queryParams(null, cid, true);

        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(query);
        queryBuilder.withPageable(PageRequest.of(0,1));

        //添加聚合条件
        specParamDTOS.forEach(specParamDTO -> {
            //规格参数名称，就是聚合名称，聚合字段需要在名称的基础上进行拼接
            String name = specParamDTO.getName();
            //添加聚合条件，聚合名称就是规格参数的名称，聚合字段，前面拼specs.后面拼.keyword
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs."+name+".keyword"));
        });

        //查询获取聚合结果
        AggregatedPage<Goods> goodsAggregatedPage = this.esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //获取到所有的聚合结果
        Aggregations aggregations = goodsAggregatedPage.getAggregations();

        //循环解析聚合结果
        specParamDTOS.forEach(specParamDTO -> {
            String name = specParamDTO.getName();

            //根据聚合名称获取到具体的聚合结果
            StringTerms terms = aggregations.get(name);

            //从聚合分桶中获取到每个聚合的key
            List<String> result = terms.getBuckets().stream().map(StringTerms.Bucket::getKeyAsString).collect(Collectors.toList());

            //把对应规格名称，对应的值，返回
            resultMap.put(name,result);
        });


    }

    //构建查询条件的方法，从基本查询改成了布尔查询，并且添加了过滤条件
    private QueryBuilder buildBasicQuery(SearchRequest searchRequest) {

        String key = searchRequest.getKey();

        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.REQUEST_PARAM_ERROR);
        }

        //把查询条件改成bool查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //添加查询条件
        boolQuery.must(QueryBuilders.matchQuery("all", key).operator(Operator.AND));

        //添加过滤条件
        Set<Map.Entry<String, String>> entries = searchRequest.getFilter().entrySet();

        entries.forEach(entry->{
            String filterKey = entry.getKey();
            String filterValue = entry.getValue();

            if ("品牌".equals(filterKey)){
                filterKey = "brandId";
            }else if ("分类".equals(filterKey)){
                filterKey = "categoryId";
            }else{
                filterKey = "specs."+filterKey+".keyword";
            }
            //添加过滤条件
            boolQuery.filter(QueryBuilders.termQuery(filterKey,filterValue));
        });

        return boolQuery;
    }
}
